import pandas as pd
import numpy as np
import plotly.graph_objects as go
from config import *
# mas imports
from nltk.tokenize import TweetTokenizer
tt = TweetTokenizer()
cargar sets
df_es_train = pickle.load(open(file_names["df_es_train"], "rb"))
df_es_trial = pickle.load(open(file_names["df_es_trial"], "rb"))
df_es_test = pickle.load(open(file_names["df_es_test"], "rb"))
pre-procesamiento
%%time
df_es_train['tokenized_text'] = df_es_train['text'].str.lower().apply(lambda x: " ".join(tt.tokenize(x)))
df_es_train.head()
CPU times: user 3.62 s, sys: 121 ms, total: 3.74 s Wall time: 3.77 s
| id | text | label | tokenized_text | |
|---|---|---|---|---|
| 0 | 793417168469757952 | Es imposible quererte más @ Plaza Del Callao -... | 0 | es imposible quererte más @ plaza del callao -... |
| 1 | 718539939063926790 | Disfrutando de buena comida con buena compañía... | 4 | disfrutando de buena comida con buena compañía... |
| 2 | 670562346067193856 | Muchísimas Felicidades M!!! Nos vemos pronto! ... | 11 | muchísimas felicidades m ! ! ! nos vemos pront... |
| 3 | 783680728538214400 | Y pensar que a esta persona la conozco de hace... | 11 | y pensar que a esta persona la conozco de hace... |
| 4 | 711210617043075073 | ¡Que buenas son las noches así y que buena com... | 17 | ¡ que buenas son las noches así y que buena co... |
df_es_test['tokenized_text'] = df_es_test['text'].str.lower().apply(lambda x: " ".join(tt.tokenize(x)))
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer(min_df=5)
X_train_bow = vectorizer.fit_transform(df_es_train["tokenized_text"])
X_test_bow = vectorizer.transform(df_es_test["tokenized_text"])
from sklearn.naive_bayes import MultinomialNB
clf = MultinomialNB()
clf.fit(X_train_bow, df_es_train["label"]);
clf.score(X_train_bow, df_es_train["label"])
0.3847601013206109
from sklearn.metrics import classification_report
df_es_mapping = pickle.load(open(file_names["df_es_mapping"], "rb")).sort_values("label")
y_pred = clf.predict(X_test_bow)
print(classification_report(df_es_test["label"], y_pred, target_names=df_es_mapping["emoji"]))
precision recall f1-score support
❤ 0.33 0.52 0.41 2141
😍 0.27 0.34 0.30 1408
😎 0.16 0.07 0.10 339
💙 0.18 0.01 0.02 413
💜 0.00 0.00 0.00 235
😜 0.14 0.02 0.04 274
💞 0.00 0.00 0.00 93
✨ 0.21 0.04 0.06 416
🎶 0.16 0.11 0.13 212
💘 0.00 0.00 0.00 134
😁 0.03 0.00 0.01 209
😂 0.41 0.63 0.50 1499
💕 0.06 0.03 0.04 352
😊 0.10 0.12 0.11 514
😘 0.21 0.10 0.14 397
💪 0.36 0.34 0.35 307
😉 0.11 0.05 0.07 453
👌 0.14 0.07 0.10 180
🇪🇸 0.32 0.39 0.36 424
accuracy 0.30 10000
macro avg 0.17 0.15 0.14 10000
weighted avg 0.25 0.30 0.26 10000
vocab = {k: v for v, k in enumerate(vectorizer.get_feature_names_out())}
vec_test = np.zeros(X_train_bow.shape[1])
k = vocab["españa"]
vec_test[k] = 1
print(vectorizer.inverse_transform([vec_test])[0][0])
clf.predict_proba([vec_test])
españa
array([[0.21389077, 0.14984582, 0.02407798, 0.02703996, 0.01742892,
0.01001667, 0.01166575, 0.01880478, 0.01127802, 0.01410191,
0.01677726, 0.04141457, 0.04917007, 0.04752564, 0.02019323,
0.01585438, 0.01578489, 0.02303667, 0.27209274]])
GridSearch
%%capture
from sklearn.metrics import f1_score, accuracy_score
dfs = [1,2,3,4,5,6,7,8,9,10]
alphas = [0, 0.2, 0.4, 0.6, 0.8, 1]
f1_m = {}
f1_w = {}
scores={}
for j in alphas:
f1_m[j] = {}
f1_w[j] = {}
scores[j] = {}
for i in dfs:
#Vectorización
vectorizer = CountVectorizer(min_df=i) #Definimos la cantidad de veces que se repite una palabra para que el clasificador la tome en consideración.
X_train_bow = vectorizer.fit_transform(df_es_train["tokenized_text"])
X_test_bow = vectorizer.transform(df_es_test["tokenized_text"])
#Obtenemos el clf score para el clasificador
clf = MultinomialNB(alpha=j)
clf.fit(X_train_bow, df_es_train["label"])
clf.score(X_train_bow, df_es_train["label"])
y_pred = clf.predict(X_test_bow)
y_true = df_es_test["label"]
# Se llenan los diccionarios
f1_m[j][i] = f1_score(y_true, y_pred, average="macro")
f1_w[j][i] = f1_score(y_true, y_pred, average="weighted")
scores[j][i] = accuracy_score(y_true, y_pred)
scores_array = []
for i in scores:
data = np.array(list(scores[i].values()) ).reshape(len(dfs),)
scores_array.append(data)
x_axis_labels = []
plt.title("Accuracy del clasificador para distintos parámetros")
sns.heatmap(scores_array, annot=True, xticklabels=dfs, yticklabels=alphas)
plt.xlabel('minimas ocurrencias para token')
plt.ylabel('alpha');
f1_m_array = []
for i in f1_m:
data = np.array(list(f1_m[i].values()) ).reshape(len(dfs),) # transform to 2D
f1_m_array.append(data)
f1_m_array
plt.title("Macro-f1 del clasificador para distintos parámetros")
sns.heatmap(f1_m_array, annot=True, xticklabels=dfs, yticklabels=alphas)
plt.xlabel('minimas ocurrencias para token')
plt.ylabel('alpha');
f1_w_array = []
for i in f1_w:
data = np.array(list(f1_w[i].values()) ).reshape(len(dfs),) # transform to 2D
f1_w_array.append(data)
f1_w_array
plt.title("Weighted-f1 del clasificador para distintos parámetros")
sns.heatmap(f1_w_array, annot=True, xticklabels=dfs, yticklabels=alphas)
plt.xlabel('minimas ocurrencias para token')
plt.ylabel('alpha');
Finalmente, como resulado de este grid-search, escogemos el clasificador con el mejor macro-f1.
best_alpha, best_min_df = np.unravel_index(np.argmax(np.array(f1_m_array)),shape=(len(alphas),len(dfs)))
scr, macf1, weif1 = scores_array[best_alpha][best_min_df], f1_m_array[best_alpha][best_min_df], f1_w_array[best_alpha][best_min_df]
best_alpha, best_min_df = alphas[best_alpha], dfs[best_min_df]
print("Parámetros escogidos:\n\talpha = {}\n\tminimas ocurrencias para token = {}".format(best_alpha,best_min_df))
print("Resultados de clasificación:\n\taccuracy = {}\n\tmacro f1 = {}\n\tweighted f1 = {}".format(scr,macf1,weif1))
Parámetros escogidos: alpha = 0.2 minimas ocurrencias para token = 5 Resultados de clasificación: accuracy = 0.2735 macro f1 = 0.1592299196449812 weighted f1 = 0.2585226651076328
vectorizer = CountVectorizer(min_df=best_min_df)
X_train_bow = vectorizer.fit_transform(df_es_train["tokenized_text"])
X_test_bow = vectorizer.transform(df_es_test["tokenized_text"])
clf = MultinomialNB(alpha=best_alpha)
clf.fit(X_train_bow, df_es_train["label"])
y_pred = clf.predict(X_test_bow)
print(classification_report(df_es_test["label"], y_pred, target_names=df_es_mapping["emoji"]))
precision recall f1-score support
❤ 0.36 0.39 0.37 2141
😍 0.28 0.30 0.29 1408
😎 0.13 0.11 0.12 339
💙 0.13 0.04 0.06 413
💜 0.10 0.03 0.05 235
😜 0.11 0.06 0.08 274
💞 0.00 0.00 0.00 93
✨ 0.22 0.11 0.14 416
🎶 0.13 0.19 0.16 212
💘 0.05 0.02 0.03 134
😁 0.05 0.04 0.04 209
😂 0.44 0.55 0.49 1499
💕 0.05 0.04 0.04 352
😊 0.11 0.14 0.12 514
😘 0.20 0.16 0.18 397
💪 0.28 0.36 0.32 307
😉 0.11 0.09 0.10 453
👌 0.08 0.11 0.10 180
🇪🇸 0.29 0.41 0.34 424
accuracy 0.27 10000
macro avg 0.16 0.17 0.16 10000
weighted avg 0.25 0.27 0.26 10000
%%time
vocab_length = X_train_bow.shape[1]
proba_matrix = np.array([clf.predict_proba(np.eye(1,vocab_length,k))[0] for k in range(vocab_length)])
CPU times: user 8.75 s, sys: 176 ms, total: 8.93 s Wall time: 1.5 s
print("Largo del vocabulario = {}".format(vocab_length))
Largo del vocabulario = 10736
def topPalabras(proba_matrix,emoji_id,k=5):
# retorna las palabras para las cuales el emoji en cuestión tiene mas probabilidad
prob = proba_matrix[:,emoji_id] # mmm
ind = np.argpartition(prob,-k)[-k:]
val = prob[ind]
palabras = [vectorizer.inverse_transform([np.eye(1,vocab_length,k)[0]])[0][0] for k in ind]
return palabras, val
i = 9
map_emojis = df_es_mapping["label"].values
print(df_es_mapping["emoji"][int(map_emojis[i])])
topPalabras(proba_matrix,i)
💘
(['ciento', 'sorbas', 'felizzzz', 'ordino', 'flechazo'], array([0.29971893, 0.35198258, 0.32238495, 0.36437439, 0.42107903]))
for i in range(19):
print(df_es_mapping["emoji"][int(map_emojis[i])])
pal, val = topPalabras(proba_matrix,i)
print(dict([(pal[j],val[j]) for j in range(len(pal))]))
❤
{'unanoche': 0.6860863379428201, 'belovedsweets': 0.6860863379428201, 'cachetita': 0.7133967002190734, 'ilovemadrid': 0.7399390301272345, 'benidormsunset': 0.6860863379428201}
😍
{'enamorada': 0.5718216196639336, 'maya': 0.6105414759238323, 'amordehermanos': 0.6267603262682746, 'losamo': 0.6846039304610873, 'preciosidad': 0.6432461755919693}
😎
{'rayban': 0.551543379333054, 'sunglasses': 0.5603550633565935, 'óptico': 0.6111180308445737, 'elguay': 0.660673286458433, 'worldwide': 0.6419866378515099}
💙
{'pals': 0.3297575681277473, 'azul': 0.3350016333528502, 'blue': 0.37336488876711676, 'cansaron': 0.48460911180633653, 'madrid2016': 0.4358695856869071}
💜
{'calera': 0.35959464754112863, 'atlantis': 0.3604829252578627, 'unid': 0.5254518517216809, 'magical': 0.48995516539934514, 'lavanda': 0.48150447645265865}
😜
{'gaymen': 0.5090172336899016, 'movember': 0.5376420398163474, 'thebestbarbershop': 0.6509312907435113, 'nopuedoparar': 0.5738833248004648, 'quiquepop': 0.6509312907435113}
💞
{'ochavada': 0.250724674740773, 'facultat': 0.25881681872272144, 'provencio': 0.26598555542213626, 'esenciales': 0.3279198365614017, 'planean': 0.3022318975150827}
✨
{'nacen': 0.34582480610273564, 'oscuridad': 0.404553090869053, 'mágicas': 0.3699446557533326, 'brilla': 0.34975734800470576, 'estrellas': 0.3932700067902005}
🎶
{'baila': 0.4377499392658804, 'cuéntame': 0.5722121226839422, 'mírala': 0.4591353770396685, 'illana': 0.4791939029402562, 'n1canalfiesta11': 0.6515203022570265}
💘
{'ciento': 0.29971892806759853, 'sorbas': 0.35198258019256873, 'felizzzz': 0.3223849548412547, 'ordino': 0.36437439188578064, 'flechazo': 0.4210790291336439}
😁
{'comete': 0.3139798480106664, 'instamoments': 0.33029923776904047, 'turística': 0.34330549396908605, 'bla': 0.3907265887170254, 'pasarse': 0.3497952185560865}
😂
{'jajajajajajajaja': 0.6315007019485125, 'caretos': 0.6666766276910938, 'parto': 0.6806120176160075, 'aburrimiento': 0.7135363112316856, 'meo': 0.8598691073304411}
💕
{'ombligo': 0.5026220206472154, 'perricos': 0.6442557798172189, 'envío': 0.564164644510615, 'gandules': 0.6442557798172189, 'atrapasueños': 0.6030029321507036}
😊
{'askdavidparejo': 0.6048248361695145, 'esmorzant': 0.6256383999389286, 'esperanza_pm': 0.6314582227369102, 'acuaman': 0.6655239709398794, 'momoa': 0.6655239709398794}
😘
{'tzayn': 0.7138583792657064, 'llevara': 0.7138583792657064, 'difusion': 0.7138583792657064, 'ayudaria': 0.7138583792657064, 'votad': 0.7138583792657064}
💪
{'quiendijomiedo': 0.630642214482262, 'wellness': 0.66474334355345, 'gymlife': 0.6649922059812844, 'altafit': 0.6700300262476342, 'empujón': 0.6930798856400404}
😉
{'gelidamenamora': 0.6043236382659811, 'whatsapp650200641': 0.6408912013002867, 'allianz': 0.668874309818772, 'martivell': 0.7339736484582075, 'bohio': 0.7776814868993555}
👌
{'nochaza': 0.3506713791873669, '080': 0.3554983893951407, 'alájar': 0.35574603962497187, 'calité': 0.4555915124593617, 'completito': 0.46869384657874197}
🇪🇸
{'europetrip2015': 0.6776747600931378, 'gaycation': 0.748647717488816, 'hiszpania': 0.748647717488816, 'vivaespaña': 0.8080750922074575, 'hispanidad': 0.7851892152947274}
Esta seccion consiste en una visualizacion de los tokens segun la codificacion que nos entrega Naive Bayes. De la seccion anterior, se pueden obtener las probabilidad de que un token pertenezca a una clase dada. En nuestro caso, a un emoji dado. Esto es:
$$ P(w \in C) = \frac{\text{\#(tweets donde $w$ es uno de sus tokens y el tweet tiene el emoji $C$)}}{\text{\#(tweets con el token $w$)}} $$De esta manera, cada token posee un vector de probabilidades. Donde la $C-$esima componente corresponde a $P(w \in C)$. Es decir,
$$\vec{w} = (P(w \in C) : \text{$C$ es un emoji})$$En particular, cada vector $\vec{w}$ es uno con tantas coordenadas como emojis (20 en Ingles). Y cada coordenada esta entre 0 y 1. Es decir, cada $\vec{w} \in [0, 1]^{\text{\#Emojis}}$.
Ahora bien, es de nuestro interes visualizar cada token segun su vector de probabilidad. Sin embargo, es necesario reducir la dimensionalidad de cada vector a una facil de interpretar (en nuestro caso 2-dimensiones). Para esto, se utiliza un metodo de reduccion de dimensionalidad denominado UMAP y ampliamente utilizado para la visualizacion de datos en altas dimensiones.
import umap.umap_ as umap
%%time
reducer = umap.UMAP(n_neighbors=15)
to_R2 = reducer.fit_transform(proba_matrix)
to_R2.shape
OMP: Info #276: omp_set_nested routine deprecated, please use omp_set_max_active_levels instead.
CPU times: user 47.3 s, sys: 688 ms, total: 48 s Wall time: 16.5 s
(10736, 2)
Luego de reducir los vectores de probabilidad a uno de bi-dimensional, visualizaremos segun dos aspectos el espacio de tokens. Primero, se colorean los vectores segun el emoji con mayor probabilidad. Por ejemplo, si el token $happy$ tiene mayor probabilidad de estar en la clase $smile$, entonces se asocia este token con dicho emoji. La razon de esto es solo para simplificar el analisis. Segundo, existen tokens con probabilidades maximas mas grandes que otras, es decir, tokens asociados a un mismo emoji (segun el criterio anterior) que poseen probabilidades distintas de pertenecer a dicha clase. Para observar esto, se visualizan los token con puntos de diferente tamaño y proporcional a tal probabilidad.
df_umap = pd.DataFrame(to_R2)
df_umap["token"] = vectorizer.get_feature_names_out()
df_umap["label"] = map_emojis[np.argmax(proba_matrix, axis=1).astype(int)]
df_umap["proba"] = np.max(proba_matrix, axis=1)
df_umap = df_umap.merge(df_es_mapping, on="label", how="left")
df_umap
| 0 | 1 | token | label | proba | emoji | name | |
|---|---|---|---|---|---|---|---|
| 0 | 3.981075 | 9.959625 | 00 | 2 | 0.142243 | 😂 | _face_with_tears_of_joy_ |
| 1 | 3.048105 | 12.807270 | 000 | 0 | 0.267381 | ❤ | _red_heart_ |
| 2 | 7.093706 | 9.134564 | 01 | 0 | 0.168436 | ❤ | _red_heart_ |
| 3 | 4.897144 | 6.150537 | 02 | 0 | 0.168031 | ❤ | _red_heart_ |
| 4 | 8.010995 | 5.631053 | 03 | 0 | 0.376696 | ❤ | _red_heart_ |
| ... | ... | ... | ... | ... | ... | ... | ... |
| 10731 | 7.112108 | 7.634966 | único | 0 | 0.227317 | ❤ | _red_heart_ |
| 10732 | 7.991124 | 6.723417 | únicos | 0 | 0.295065 | ❤ | _red_heart_ |
| 10733 | 2.453170 | 7.819929 | スペイン | 4 | 0.364940 | 😊 | _smiling_face_with_smiling_eyes_ |
| 10734 | 2.668646 | 4.042776 | 세비야 | 9 | 0.602929 | 🇪🇸 | _Spain_ |
| 10735 | 2.669938 | 4.044263 | 스페인광장 | 9 | 0.602929 | 🇪🇸 | _Spain_ |
10736 rows × 7 columns
data = []
for label in df_es_mapping["label"]:
sub_df = df_umap[df_umap["label"] == label]
data.append(
go.Scattergl(
x = sub_df[0],
y = sub_df[1],
mode='markers',
text=sub_df["token"]+"<br>"+sub_df["emoji"]+"<br>"+sub_df["proba"].apply(lambda x: str(np.round(x, 3))),
name=sub_df["emoji"].iloc[0],
marker=dict(
size=25*sub_df["proba"],
line_width=0.2,
)
)
)
fig = go.Figure(data=data)
fig.update_layout(
title="Proyección (UMAP) de vectores de probabilidad de tokens",
autosize=False,
width=700,
height=500,
)
fig.show(renderer="notebook")
Comentarios El top 5 de la seccion anterior se puede capturar con los primero cinco punto de mayor tamaño para un emoji dado. Tambien, se observa que la clase con mas puntos corresponde al emoji del corazon. Mismo emoji con mayor popularidad visto en la etapa de analisis de los datos. Se observan grupos diferenciados, pero que logran solaparse. Esta zona coincide con aquellos tokens con probabilidades uniformes de pertenecer a cada clase y/o con probabilidad maxima cercanas a 0.1.
Distribución de clases usando subsampling
df_es_train1 = pickle.load(open(file_names["df_es_train"], "rb"))
print("Distribucion de clases original")
counts = df_es_train1['label'].value_counts()
counts
Distribucion de clases original
0 16102 1 11429 2 7725 4 5448 3 5348 5 3660 6 3124 7 3117 8 2884 9 2757 10 2610 11 2357 13 2289 12 2211 18 2203 16 2112 14 2041 15 2006 17 1903 Name: label, dtype: int64
min_freq = np.min(counts.values)
min_class = list(counts.index)[np.argmin(counts.values)]
print("Mínima frecuencia entre las distintas clases = {}\nEmoji class = {}".format(min_class,min_class))
Mínima frecuencia entre las distintas clases = 17 Emoji class = 17
reduce_index = list(counts.index)
reduce_index.remove(min_class)
for label in reduce_index:
freq = counts[label]
delete_counts = freq - min_freq
df_es_train1 = df_es_train1.reset_index(drop=True)
## subsampling sobre la clase label
idx = np.random.choice(df_es_train1.loc[df_es_train1.label == label].index, size=delete_counts, replace=False)
data_subsampled = df_es_train1.drop(df_es_train1.iloc[idx].index, inplace = True)
%%time
df_es_train1['tokenized_text'] = df_es_train1['text'].str.lower().apply(lambda x: " ".join(tt.tokenize(x)))
df_es_train1.head()
CPU times: user 1.75 s, sys: 7.91 ms, total: 1.75 s Wall time: 1.76 s
| id | text | label | tokenized_text | |
|---|---|---|---|---|
| 0 | 670562346067193856 | Muchísimas Felicidades M!!! Nos vemos pronto! ... | 11 | muchísimas felicidades m ! ! ! nos vemos pront... |
| 1 | 711210617043075073 | ¡Que buenas son las noches así y que buena com... | 17 | ¡ que buenas son las noches así y que buena co... |
| 2 | 745615686655967232 | Un placer haberos conocido @ Colegio Santa Vic... | 0 | un placer haberos conocido @ colegio santa vic... |
| 3 | 670341440862560256 | @user yo te lo desaburro guapetón | 14 | @user yo te lo desaburro guapetón |
| 4 | 665984610812215297 | San Martín '15 inmejorables @ Cardeñadijo, Spain | 6 | san martín ' 15 inmejorables @ cardeñadijo , s... |
df_es_test['tokenized_text'] = df_es_test['text'].str.lower().apply(lambda x: " ".join(tt.tokenize(x)))
vectorizer = CountVectorizer(min_df=10)
X_train_bow1 = vectorizer.fit_transform(df_es_train1["tokenized_text"])
X_test_bow = vectorizer.transform(df_es_test["tokenized_text"])
clf = MultinomialNB(alpha = .2)
clf.fit(X_train_bow1, df_es_train1["label"])
clf.score(X_train_bow1, df_es_train1["label"])
0.34621235168846975
df_es_mapping = pickle.load(open(file_names["df_es_mapping"], "rb")).sort_values("label")
y_pred = clf.predict(X_test_bow)
print(classification_report(df_es_test["label"], y_pred, target_names=df_es_mapping["emoji"]))
precision recall f1-score support
❤ 0.37 0.06 0.11 2141
😍 0.30 0.11 0.16 1408
😎 0.10 0.16 0.12 339
💙 0.09 0.07 0.07 413
💜 0.05 0.06 0.05 235
😜 0.04 0.05 0.04 274
💞 0.01 0.04 0.02 93
✨ 0.14 0.15 0.14 416
🎶 0.08 0.27 0.12 212
💘 0.04 0.10 0.05 134
😁 0.04 0.09 0.06 209
😂 0.43 0.33 0.37 1499
💕 0.05 0.04 0.05 352
😊 0.09 0.08 0.08 514
😘 0.16 0.28 0.21 397
💪 0.22 0.37 0.28 307
😉 0.09 0.14 0.11 453
👌 0.05 0.17 0.08 180
🇪🇸 0.23 0.46 0.31 424
accuracy 0.16 10000
macro avg 0.14 0.16 0.13 10000
weighted avg 0.24 0.16 0.16 10000
Conclusiones
A partir del subsampling, se puede observar que los resultados no presentan una mejorar e incluso en varias clases disminuye la efectividad del clasificador, por lo que es mejor no hacer el subsampling para obtener mejores metricas.